using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using StardewModdingAPI;
using StardewModdingAPI.Toolkit.Framework.Clients.CompatibilityRepo;

namespace SMAPI.Tests.WikiClient;

/// <summary>Unit tests for <see cref="ChangeDescriptor"/>.</summary>
[TestFixture]
internal class ChangeDescriptorTests
{
    /*********
    ** Unit tests
    *********/
    /****
    ** Constructor
    ****/
    [Test(Description = "Assert that Parse sets the expected values for valid and invalid descriptors.")]
    public void Parse_SetsExpectedValues_Raw()
    {
        // arrange
        string rawDescriptor = "-Nexus:2400,    -B, XX → YY, Nexus:451,+A, XXX → YYY, invalidA →, → invalidB";
        string[] expectedAdd = ["Nexus:451", "A"];
        string[] expectedRemove = ["Nexus:2400", "B"];
        IDictionary<string, string> expectedReplace = new Dictionary<string, string>
        {
            ["XX"] = "YY",
            ["XXX"] = "YYY"
        };
        string[] expectedErrors =
        [
            "Failed parsing ' invalidA →': can't map to a blank value. Use the '-value' format to remove a value.",
            "Failed parsing ' → invalidB': can't map from a blank old value. Use the '+value' format to add a value."
        ];

        // act
        ChangeDescriptor parsed = ChangeDescriptor.Parse(rawDescriptor, out string[] errors);

        // assert
        parsed.Add.Should().BeEquivalentTo(expectedAdd);
        parsed.Remove.Should().BeEquivalentTo(expectedRemove);
        parsed.Replace.Should().BeEquivalentTo(expectedReplace);
        errors.Should().BeEquivalentTo(expectedErrors);
    }

    [Test(Description = "Assert that Parse sets the expected values for descriptors when a format callback is specified.")]
    public void Parse_SetsExpectedValues_Formatted()
    {
        // arrange
        string rawDescriptor = "-1.0.1,    -2.0-beta, 1.00 → 1.0, 1.0.0,+2.0-beta.15, 2.0 → 2.0-beta, invalidA →, → invalidB";
        string[] expectedAdd = ["1.0.0", "2.0.0-beta.15"];
        string[] expectedRemove = ["1.0.1", "2.0.0-beta"];
        IDictionary<string, string> expectedReplace = new Dictionary<string, string>
        {
            ["1.00"] = "1.0.0",
            ["2.0.0"] = "2.0.0-beta"
        };
        string[] expectedErrors =
        [
            "Failed parsing ' invalidA →': can't map to a blank value. Use the '-value' format to remove a value.",
            "Failed parsing ' → invalidB': can't map from a blank old value. Use the '+value' format to add a value."
        ];

        // act
        ChangeDescriptor parsed = ChangeDescriptor.Parse(
            rawDescriptor,
            out string[] errors,
            formatValue: raw => SemanticVersion.TryParse(raw, out ISemanticVersion? version)
                ? version.ToString()
                : raw
        );

        // assert
        parsed.Add.Should().BeEquivalentTo(expectedAdd);
        parsed.Remove.Should().BeEquivalentTo(expectedRemove);
        parsed.Replace.Should().BeEquivalentTo(expectedReplace);
        errors.Should().BeEquivalentTo(expectedErrors);
    }

    [Test(Description = "Assert that Apply returns the expected value for the given descriptor.")]

    // null input
    [TestCase(null, "", ExpectedResult = null)]
    [TestCase(null, "+Nexus:2400", ExpectedResult = "Nexus:2400")]
    [TestCase(null, "-Nexus:2400", ExpectedResult = null)]

    // blank input
    [TestCase("", null, ExpectedResult = "")]
    [TestCase("", "", ExpectedResult = "")]

    // add value
    [TestCase("", "+Nexus:2400", ExpectedResult = "Nexus:2400")]
    [TestCase("Nexus:2400", "+Nexus:2400", ExpectedResult = "Nexus:2400")]
    [TestCase("Nexus:2400", "Nexus:2400", ExpectedResult = "Nexus:2400")]
    [TestCase("Nexus:2400", "+Nexus:2401", ExpectedResult = "Nexus:2400, Nexus:2401")]
    [TestCase("Nexus:2400", "Nexus:2401", ExpectedResult = "Nexus:2400, Nexus:2401")]

    // remove value
    [TestCase("", "-Nexus:2400", ExpectedResult = "")]
    [TestCase("Nexus:2400", "-Nexus:2400", ExpectedResult = "")]
    [TestCase("Nexus:2400", "-Nexus:2401", ExpectedResult = "Nexus:2400")]

    // replace value
    [TestCase("", "Nexus:2400 → Nexus:2401", ExpectedResult = "")]
    [TestCase("Nexus:2400", "Nexus:2400 → Nexus:2401", ExpectedResult = "Nexus:2401")]
    [TestCase("Nexus:1", "Nexus: 2400 → Nexus: 2401", ExpectedResult = "Nexus:1")]

    // complex strings
    [TestCase("", "+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "Nexus:A, Nexus:B")]
    [TestCase("Nexus:2400", "+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "Nexus:2401, Nexus:A, Nexus:B")]
    [TestCase("Nexus:2400, Nexus:2401, Nexus:B,Chucklefish:14", "+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "Nexus:2401, Nexus:2401, Nexus:B, Nexus:A")]
    public string Apply_Raw(string input, string? descriptor)
    {
        ChangeDescriptor parsed = ChangeDescriptor.Parse(descriptor, out string[] errors);

        errors.Should().BeEmpty();

        return parsed.ApplyToCopy(input);
    }

    [Test(Description = "Assert that ToString returns the expected normalized descriptors.")]
    [TestCase(null, ExpectedResult = "")]
    [TestCase("", ExpectedResult = "")]
    [TestCase("+   Nexus:2400", ExpectedResult = "+Nexus:2400")]
    [TestCase("  Nexus:2400  ", ExpectedResult = "+Nexus:2400")]
    [TestCase("-Nexus:2400", ExpectedResult = "-Nexus:2400")]
    [TestCase("  Nexus:2400   →Nexus:2401  ", ExpectedResult = "Nexus:2400 → Nexus:2401")]
    [TestCase("+Nexus:A, Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A→Nexus:B", ExpectedResult = "+Nexus:A, +Nexus:B, -Chucklefish:14, Nexus:2400 → Nexus:2401, Nexus:A → Nexus:B")]
    public string ToString(string? descriptor)
    {
        var parsed = ChangeDescriptor.Parse(descriptor, out string[] errors);

        errors.Should().BeEmpty();

        return parsed.ToString();
    }
}
